package zf.data.crypto
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import flash.utils.Timer;
	
	import zf.data.tools.Memory;
	
	/**
	 * 解密完成时触发
	 * @eventType zf.data.crypto.XXTEAAsynEvent.DECRYPT_COMPLETE
	 */
	[Bindable]
	[Event(name="decrypt_complete", type="zf.data.crypto.XXTEAAsynEvent")]
	
	/**
	 * 加密完成时触发
	 * @eventType zf.data.crypto.XXTEAAsynEvent.ENCRYPT_COMPLETE
	 */
	[Bindable]
	[Event(name="encrypt_complete", type="zf.data.crypto.XXTEAAsynEvent")]
	
	/**
	 * 进程事件
	 * @eventType zf.data.crypto.XXTEAAsynEvent.PROGRESS
	 */
	[Bindable]
	[Event(name="progress", type="zf.data.crypto.XXTEAAsynEvent")]
	
	public class XXTEAAsyn extends EventDispatcher
	{
		private const delta:uint = uint(0x9E3779B9);
		
		private var keyByte:ByteArray;
		
		private var timer:Timer;
		
		private var timeInterval:uint;	
		private var timeTotalRun:uint;
		
		private var length:uint;
		private var zn:uint;
		private var zm:uint;
		private var resultByte:ByteArray;
		private var resultArray:Array;
		private var zi:uint;
		private var inArray:Array;
		private var inByte:ByteArray;
		private var includeLengthAsyn:Boolean;
		
		private var v:Array;
		private var k:Array;
	
		private var n:uint;
		private var z:uint;
		private var y:uint;
		private var mx:uint;
		private var e:uint;
		private var p:uint;
		private var q:uint;
		private var sum:uint;
		private var max:Number;
		private var np:uint;
		
		private var runId:uint;
		private var runFunList:Array;
		
		private var _running:Boolean=false;
		
		/**
		 * XXTEA加密算法的异步执行类 
		 * @param key 二进制数组密匙
		 * @param interval 异步执行时间间隔
		 * @param total 每个时间间隔执行次数
		 * 
		 */
		public function XXTEAAsyn(key:ByteArray,interval:uint=40,total:uint=50000)
		{
			timeInterval=interval;
			timeTotalRun=total;

			keyByte=key;
			/*k = ByteArrayToLongArray(keyByte,false);
			if (k.length < 4)
			{
				k.length = 4;
			}*/
		}

		/**
		 * 是否在运行状态 
		 * @return 
		 * 
		 */
		public function get running():Boolean
		{
			return _running;
		}
		/**
		 * ByteArray二进制数组转化为长数组
		 * @param data 二进制数组
		 * @param includeLength 是否包括长度，如果包括长度，数组最后一位是长度值
		 * @return 数组
		 * @see LongArrayToByteArray
		 */
		private function ByteArrayToLongArray(data:ByteArray, includeLength:Boolean=false):Array
		{
			var length:uint = data.length;
			var n:uint = length >> 2;
			if (length % 4 > 0)
			{
				n++;
				data.length +=  (4 - (length % 4));
			}
			data.endian = Endian.LITTLE_ENDIAN;
			data.position = 0;
			var result:Array = [];
			for (var i:uint = 0; i < n; i++)
			{
				result[i] = data.readUnsignedInt();
			}
			if (includeLength)
			{
				result[n] = length;
			}
			data.length = length;
			return result;
		}
		/**
		 * 长数组转化为二进制数组 
		 * @param data 数组
		 * @param cludeLength 数组是否包含长度信息，data最后一位是否为长度
		 * @return 二进制数组 
		 * @see ByteArrayToLongArrayAsyn
		 */
		private function LongArrayToByteArrayAsyn(data:Array, cludeLength:Boolean=false):void
		{
			inArray=data;
			includeLengthAsyn=cludeLength;
			length = inArray.length;
			zn = (length - 1) << 2;
			if (includeLengthAsyn)
			{
				zm = inArray[length - 1];
				
				if ((zm < zn - 3) || (zm > zn))
				{
					return;
				}
				zn = zm;
			}
			resultByte = new ByteArray();
			resultByte.endian = Endian.LITTLE_ENDIAN;
			zi=0;
			timer=new Timer(timeInterval);
			timer.addEventListener(TimerEvent.TIMER,onLatbaTimerHander);
			//timer.addEventListener(TimerEvent.TIMER_COMPLETE,onLatbaTimerCompleteHander);
			timer.start();
			//return resultByte;
		}
		private function onLatbaTimerHander(e:TimerEvent):void
		{
			for(var i:uint=0;i<timeTotalRun && zi<length;i++)
			{
				resultByte.writeUnsignedInt(inArray[zi]);
				zi++;
			}
			sendProgress(zi,length);
			if(zi>=length)
			{
				
				onLatbaTimerCompleteHander(null);
			}
		}
		private function onLatbaTimerCompleteHander(e:TimerEvent):void
		{
			if (includeLengthAsyn)
			{
				resultByte.length = inArray[length-1];
			}
			inArray=null;
			timer.stop();
			timer.removeEventListener(TimerEvent.TIMER,onLatbaTimerHander);
			timer=null;
			//timer.removeEventListener(TimerEvent.TIMER_COMPLETE,onLatbaTimerCompleteHander);
			//事件
			outEnd();
		}
		/**
		 * ByteArray二进制数组转化为长数组
		 * @param data 二进制数组
		 * @param cludeLength 是否包括长度，如果包括长度，数组最后一位是长度值
		 * @return 数组
		 * @see LongArrayToByteArrayAsyn
		 */
		private function ByteArrayToLongArrayAsyn(data:ByteArray, cludeLength:Boolean=false):void
		{
			inByte=data;
			includeLengthAsyn=cludeLength;
			length = inByte.length;
			zn = length >> 2;
			if (length % 4 > 0)
			{
				zn++;
				inByte.length +=  (4 - (length % 4));
			}
			inByte.endian = Endian.LITTLE_ENDIAN;
			inByte.position = 0;
			resultArray = new Array();
			zi=0;
			timer=new Timer(timeInterval);
			timer.addEventListener(TimerEvent.TIMER,onBatlaTimerHander);
			//timer.addEventListener(TimerEvent.TIMER_COMPLETE,onBatlaTimerCompleteHander);
			timer.start();
			//return resultArray;
		}
		private function onBatlaTimerHander(e:TimerEvent):void
		{
			for(var i:uint=0;i<timeTotalRun && zi<zn;i++)
			{
				resultArray[zi] = inByte.readUnsignedInt();
				zi++;
			}
			
			sendProgress(zi,zn);
			if(zi>=zn)
			{
				
				onBatlaTimerCompleteHander(null);
			}
		}
		private function onBatlaTimerCompleteHander(e:TimerEvent):void
		{
			if (includeLengthAsyn)
			{
				resultArray[zn] = length;
			}
			//inByte.length = length;
			inByte.clear();
			timer.stop();
			timer.removeEventListener(TimerEvent.TIMER,onBatlaTimerHander);
			timer=null;
			//timer.removeEventListener(TimerEvent.TIMER_COMPLETE,onBatlaTimerCompleteHander);
			//事件;
			outEnd();
		}
		
		private function sendProgress(bl:Number,bt:Number):void
		{
			bl=bl?bl:0;
			bt=bt?bt:1;
			this.dispatchEvent(new XXTEAAsynEvent(XXTEAAsynEvent.PROGRESS,null,runId,runFunList.length,bl,bt));
		}
		private function outEnd():void
		{
			if(runId<runFunList.length)
			{
				var fun:Function=runFunList[runId];
				runId++;
				fun();
			}
		}
		
		/**
		 * 加密 
		 * @param data 待加密的ByteArray;
		 * @return 加密的ByteArray
		 * 
		 */
		public function encryptAsyn(data:ByteArray):void
		{
			if(_running)
			{
				return;
			}
			if (data.length == 0)
			{
				//事件
				return;
			}
			//data.compress();
			_running=true;
			runId=0;
			runFunList=[encrypt1,encrypt2,encrypt3];
			
			ByteArrayToLongArrayAsyn(data,true);
		}
		private function encrypt1():void
		{
			v=resultArray;
			k = ByteArrayToLongArray(keyByte,false);			
			if (k.length < 4)
			{
				k.length = 4;
			}
			
			n = v.length - 1;
			z = v[n];
			y = v[0];
			q = uint(6 + 52 / (n + 1));
			max=q*n;
			sum = 0;
			p=n;
			timer=new Timer(timeInterval);
			timer.addEventListener(TimerEvent.TIMER,onEncrypt1Hander);
			timer.start();
		}
		private function onEncrypt1Hander(event:TimerEvent):void
		{
			//trace(runId);
			
			//for(var i:uint=0;i<timeTotalRun && 0 < q;i++)
			if(q>0)
			{
				
				if(p==n)
				{
					p=0;
					sum = sum + delta;
					e = sum >>> 2 & 3;					
				}
				sendProgress(max-q*n+p,max);
				//for (p = 0; p < n; p++)	
				for(var i:uint=0;i<timeTotalRun && p<n;i++)
				{
					y = v[p + 1];
					mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
					z = v[p] = v[p] + mx;
					p++;
				}
				if(p==n)
				{
					y = v[0];
					mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
					z = v[n] = v[n] + mx;
					q--;
				}
			}
			else
			{
				timer.stop();
				timer.removeEventListener(TimerEvent.TIMER,onEncrypt1Hander);
				timer=null;
				outEnd();
			}
		}
		private function encrypt2():void
		{
			LongArrayToByteArrayAsyn(v,false);
		}
		private function encrypt3():void
		{
			//发送事件
			clearMemory();
			Memory.gc();
			_running=false;
			
			this.dispatchEvent(new XXTEAAsynEvent(XXTEAAsynEvent.ENCRYPT_COMPLETE,resultByte));
		}
		/**
		 * 解密 
		 * @param data 加密的ByteArray
		 * @return 解密ByteArray
		 * 
		 */
		public function decryptAsyn(data:ByteArray):void
		{
			if(_running)
			{
				return;
			}
			if (data.length == 0)
			{
				return;
			}
			_running=true;
			runId=0;
			runFunList=[decrypt1,decrypt2,decrypt3];
			ByteArrayToLongArrayAsyn(data,false);
		}
		private function decrypt1():void
		{
			v=resultArray;
			k = ByteArrayToLongArray(keyByte,false);			
			if (k.length < 4)
			{
				k.length = 4;
			}
			
			n = v.length - 1;
			z = v[n - 1];
			y = v[0];
			q = uint(6 + 52 / (n + 1));
			sum = q * delta;
			max=q*n;
			p=0;
			timer=new Timer(timeInterval);
			timer.addEventListener(TimerEvent.TIMER,onDecrypt1Hander);
			timer.start();
		}
		private function onDecrypt1Hander(event:TimerEvent):void
		{
			//sendProgress(max-q,max);
			if(q>0)
			{
				
				if(p==0)
				{
					p=n;
					e = sum >>> 2 & 3;
				}
				sendProgress(max-q*n+n-p,max);
				//for (p = n; p > 0; p--)
				for(var i:uint=0;i<timeTotalRun && p>0;i++)														
				{
					z = v[p - 1];
					mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
					y = v[p] = v[p] - mx;
					p--;					
				}
				
				if(p==0)
				{
					z = v[n];
					mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
					y = v[0] = v[0] - mx;
					sum = sum - delta;
					q--;
				}
			}
			else
			{
				timer.stop();
				timer.removeEventListener(TimerEvent.TIMER,onDecrypt1Hander);
				timer=null;
				outEnd();
			}
		}
		private function decrypt2():void
		{
			LongArrayToByteArrayAsyn(v, true);
		}
		private function decrypt3():void
		{
			//resultByte.uncompress();
			clearMemory();
			Memory.gc();
			_running=false;
			
			this.dispatchEvent(new XXTEAAsynEvent(XXTEAAsynEvent.DECRYPT_COMPLETE,resultByte));
		}
		
		private function clearMemory():void
		{
			resultArray=null;
			inArray=null;
			v=null;
			k=null;
		}
	}
}